Security Hubのアラートを Microsoft Teams に通知する仕組みをCloudFormationテンプレート化
はじめに
Security Hubのアラートを Microsoft Teams に通知する仕組みを CloudFormation テンプレートで作成しました。
以前、Security Hub で検知した内容を Amazon EventBridge 経由で Microsoft Teams に通知する仕組みの構築方法を執筆しました。
今回は、通知する仕組みをCloudFormationテンプレートで構築できるようにしましたので、紹介します。
通知される画面は、以下の通りです。
個人的にEventBridge API送信先やEventBridge 接続をテンプレートで作成することがなかったので、勉強になりました。
構成図
構成は以下の通りです。
作成されるリソース
CloudFormationで作成するリソースは、以下の通りです。
- EventBridge
- API送信先
- 接続
- ルール
- IAMロール
- IAMポリシー
テンプレートで、EventBridge API 送信先とEventBridge 接続を作成すると、以下の2点のリソースが自動作成されます
- AWS Secrets Managerのシークレット
- サービスリンクロール(
AWSServiceRoleForAmazonEventBridgeApiDestinations
)
1点目は、EventBridge 接続の情報を保存するSecrets Managerのシークレットが自動作成されます。
マネジメントコンソール上では、EventBridge 接続から自動作成されたSecrets Managerのシークレットに遷移できます。
2点目は、AWSServiceRoleForAmazonEventBridgeApiDestinations
というサービスリンクロールが自動作成されます。
EventBridgeが、Secrets Managerのシークレットを作成して保存するために、サービスリンクされたロールを使用します。
ロールには、AmazonEventBridgeApiDestinationsServiceRolePolicy
というポリシーがアタッチされています。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "secretsmanager:CreateSecret", "secretsmanager:UpdateSecret", "secretsmanager:DescribeSecret", "secretsmanager:DeleteSecret", "secretsmanager:GetSecretValue", "secretsmanager:PutSecretValue" ], "Resource": "arn:aws:secretsmanager:*:*:secret:events!connection/*" } ] }
ちなみに、スタック削除した際、Secrets Managerのシークレットは自動削除され、サービスリンクロールは残ります。
テンプレート
テンプレート内容は以下の通りです
コード (クリックすると展開します)
AWSTemplateFormatVersion: '2010-09-09' Description: '' Parameters: SystemPrefix: Type: String EnvPrefix: Type: String WebhookURL: Type: String MentionedUserMailAddress: Description: [email protected] Type: String MentionedUserName: Type: String Resources: IAMManagedPolicy: Type: AWS::IAM::ManagedPolicy Properties: ManagedPolicyName: EventBridgePolicyForSecurityHubNotifytoTeams Path: /service-role/ PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - events:InvokeApiDestination Resource: - !GetAtt EventsApiDestination.Arn IAMRole: Type: AWS::IAM::Role Properties: Path: /service-role/ RoleName: !Sub ${SystemPrefix}-${EnvPrefix}-eventbridge-teams-api-dest-role AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole MaxSessionDuration: 3600 ManagedPolicyArns: - !Ref IAMManagedPolicy EventsRule: Type: AWS::Events::Rule Properties: Name: !Sub ${SystemPrefix}-${EnvPrefix}-securityhub-notify-teams EventPattern: detail-type: - Security Hub Findings - Imported source: - aws.securityhub detail: findings: Severity: Label: - HIGH - CRITICAL ProductName: - Security Hub State: ENABLED Targets: - Arn: !GetAtt EventsApiDestination.Arn HttpParameters: HeaderParameters: {} QueryStringParameters: {} Id: EventsRuleName InputTransformer: InputPathsMap: AwsAccountId: '$.detail.findings[0].AwsAccountId' FirstObservedAt: '$.detail.findings[0].FirstObservedAt' LastObservedAt: '$.detail.findings[0].LastObservedAt' RecommendationUrl: '$.detail.findings[0].ProductFields.RecommendationUrl' Region: '$.detail.findings[0].Resources[0].Region' ResourceId: '$.detail.findings[0].Resources[0].Id' ResourceType: '$.detail.findings[0].Resources[0].Type' SeverityLabel: '$.detail.findings[0].Severity.Label' Title: '$.detail.findings[0].Title' InputTemplate: !Sub | { "type": "message", "attachments": [ { "contentType": "application/vnd.microsoft.card.adaptive", "content": { "type": "AdaptiveCard", "body": [ { "type": "TextBlock", "text": "\u003cat\u003e${MentionedUserName}\u003c/at\u003e", "weight": "bolder", "size": "medium" }, { "type": "TextBlock", "text": "SecurityHubで重大度<SeverityLabel>のアラートを検知しました", "size": "Large", "weight": "Bolder" }, { "type": "Table", "columns": [ { "width": 1 }, { "width": 2 } ], "rows": [ { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "タイトル", "weight": "Bolder" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "wrap": true, "text": "<Title>" } ] } ] }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "重要度", "weight": "Bolder" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "wrap": true, "text": "<SeverityLabel>" } ] } ] }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "修復手順", "weight": "Bolder" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "wrap": true, "text": "[こちらをクリック](<RecommendationUrl>)" } ] } ] }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "アカウントID", "weight": "Bolder" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "wrap": true, "text": "<AwsAccountId>" } ] } ] }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "リージョン", "weight": "Bolder" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "wrap": true, "text": "<Region>" } ] } ] }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "リソースタイプ", "weight": "Bolder" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "wrap": true, "text": "<ResourceType>" } ] } ] }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "リソースID", "weight": "Bolder" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "wrap": true, "text": "<ResourceId>" } ] } ] }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "初回検出日時", "weight": "Bolder" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "wrap": true, "text": "<FirstObservedAt>" } ] } ] }, { "type": "TableRow", "cells": [ { "type": "TableCell", "items": [ { "type": "TextBlock", "text": "最終検出日時", "weight": "Bolder" } ] }, { "type": "TableCell", "items": [ { "type": "TextBlock", "wrap": true, "text": "<LastObservedAt>" } ] } ] } ] } ], "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.5", "msteams": { "width": "full", "entities": [ { "type": "mention", "text": "\u003cat\u003e${MentionedUserName}\u003c/at\u003e", "mentioned": { "id": "${MentionedUserMailAddress}", "name": "${MentionedUserName}" } } ] } } } ] } RoleArn: !GetAtt IAMRole.Arn EventBusName: default EventsConnection: Type: AWS::Events::Connection Properties: Name: !Sub ${SystemPrefix}-${EnvPrefix}-teams-conn AuthorizationType: API_KEY AuthParameters: ApiKeyAuthParameters: ApiKeyName: Content-Type ApiKeyValue: application/json EventsApiDestination: Type: AWS::Events::ApiDestination Properties: Name: !Sub ${SystemPrefix}-${EnvPrefix}-teams-api-dest ConnectionArn: !GetAtt EventsConnection.Arn InvocationEndpoint: !Ref WebhookURL HttpMethod: POST InvocationRateLimitPerSecond: 300 Outputs: EventsApiDestinationArn: Description: The ARN of the EventBridge API Destination Value: !GetAtt EventsApiDestination.Arn Export: Name: EventsApiDestinationArn
今回のテンプレートでは、Teamsに通知される画面は表形式です。
表形式ではなく箇条書きにしたい場合、前回の記事でEventBridgeの入力トランスフォーマーのテンプレートや入力パスを様々紹介していますので、ご参照ください。
CloudFormation スタック作成
パラメータは、以下の値を入れます。
- SystemPrefix
- システム名
- EnvPrefix
- 環境名
- WebhookURL
- TeamsのWebhookURL(取得方法は、こちらのブログをご参照ください。)
- MentionedUserMailAddress
- メンション先のメールアドレス
- MentionedUserName
- メンション先の名前
1分半でデプロイが完了しました。
通知の確認
Security Hubのアラートは、以下のように通知されました。
ただし、数回試したところ、同じ通知が重複することが確認されました。
これは、 EventBridgeで起こり得るとAWSドキュメントに記載がありました。
まれに、単一のイベントまたはスケジュールされた期間に対して同じルールが複数回実行されたり、トリガーされる特定のルールに対して同じターゲットを複数回呼び出されたりする場合があります。 引用
重複させない方法もあるようですが、通知の仕組み自体は、重複してもさして問題ないと思います。気になる方は以下もご確認ください。
最後に
本記事では、Security Hubのアラートを Microsoft Teams に通知する仕組みを CloudFormation テンプレートで作成する方法を紹介しました。
複数のアカウントに通知する場合や、アカウントごとにメンション先を変更する必要がある場合に参考になれば幸いです。